﻿using Abp.AspNetCore;
using Abp.Extensions;
using Abp.Runtime.Session;
using EasyCaching.InMemory;
using EasyCaching.Interceptor.AspectCore;
using Hicap.AuthorizationServer.Core;
using Hicap.AuthorizationServer.Core.Common;
using Hicap.AuthorizationServer.Core.Common.EFCore;
using Hicap.AuthorizationServer.Core.Identity;
using Hicap.AuthorizationServer.Core.IdentityServer;
using Hicap.AuthorizationServer.Core.Interceptor;
using Hicap.AuthorizationServer.Core.Notify;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Cors;
using Microsoft.AspNetCore.Mvc.Cors.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Logging;
using Serilog;
using Serilog.Events;
using Swashbuckle.AspNetCore.Swagger;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using LoggerConfiguration = Serilog.LoggerConfiguration;

namespace Hicap.AuthorizationServer.WebApi
{
    /// <summary>
    /// Startup
    /// </summary>
    /// <remarks>
    /// Reference
    /// ABP Framework:https://aspnetboilerplate.com/
    /// EasyCacheing:https://github.com/dotnetcore/EasyCaching
    /// AspectCore:https://github.com/dotnetcore/AspectCore-Framework
    /// FluentFtp:https://github.com/robinrodricks/FluentFTP
    /// Netcore 2.1:https://www.microsoft.com/net
    /// AutoMapper:http://automapper.org/
    /// Mongodb C# Driver:https://docs.mongodb.com/ecosystem/drivers/csharp/
    /// HangFire:https://www.hangfire.io/
    /// SeriLog:https://serilog.net/
    /// ...more references in nuget installed
    /// </remarks>
    public class Startup
    {
        private const string DefaultCorsPolicyName = "default";
        private IConfiguration Configuration { get; }

        private string MySqlConnectionString { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;

            MySqlConnectionString = Configuration.GetConnectionString(
                "Hicap.AuthorizationServer");
        }

        /// <summary>
        /// Configure services
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <returns></returns>
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            //Serilog
            AddLogging(services);

            //Default DbContext
            AddDbContext(services);

            AddTransient(services);

            //IdentityServer4
            AddIdentityServer(services);

            //authentication
            AddAuthentication(services);

            //SignalR
            AddSignalR(services);

            //Cors
            AddCors(services);

            //Mvc Filter
            AddMvc(services);

            //UnitOfWork
            AddUnitOfWork(services);

            //Swagger
            // AddSwaggerGen(services);

            AddSwagger(services);

            //EasyCaching in memory
            AddDefaultInMemoryCache(services);

            //ABP module and make controllers for appServices
            return AddAbp(services);
        }

        /// <summary>
        /// Configure Serilog
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <remarks>
        ///output:
        /// 1.console
        /// 2.error.log
        /// 3.info.log
        /// </remarks>
        private void AddLogging(IServiceCollection services)
        {
            Serilog.Core.Logger logger = new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Information)
                .MinimumLevel.Override("System", LogEventLevel.Information)
                .MinimumLevel.Override("DotNetCore", LogEventLevel.Information)
                .WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Verbose)
                .WriteTo.File("logs\\info\\log-.log", rollingInterval: RollingInterval.Day, restrictedToMinimumLevel: LogEventLevel.Information)
                .WriteTo.File("logs\\error\\log-.log", rollingInterval: RollingInterval.Day,
                    restrictedToMinimumLevel: LogEventLevel.Warning).CreateLogger();

            Log.Logger = logger;

            services.AddLogging(x =>
            {
                x.SetMinimumLevel(LogLevel.Debug);
                x.AddSerilog(logger);
            });
        }

        /// <summary>
        /// EFCore DbContext
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddDbContext(IServiceCollection services)
        {
            services.AddSingleton(typeof(IEnumerable<DbContextOptions>), new List<DbContextOptions>() { (new DbContextOptionsBuilder<AuthorizationDbContext>()).Options });

            services.AddDbContextPool<AuthorizationDbContext>(options =>
            {
                AuthorizationDbContextConfigurer.Configure(options, Configuration.GetConnectionString(SysConsts.ConnectionName));
            });
        }

        private void AddTransient(IServiceCollection services)
        {
            services.AddScoped<CorsAuthorizationFilter, CorsAuthorizationFilter>();
            services.AddScoped<HicapAuthorizationFilter, HicapAuthorizationFilter>();

            services.AddScoped<IAbpSession, HicapSession>();
            services.AddScoped<IPrincipalAccessor, HicapPrincipalAccessor>();

            services.AddSingleton<EasyCachingInterceptor>();
            services.AddSingleton<MethodExecuteLoggerInterceptor>();
        }

        private void AddIdentityServer(IServiceCollection services)
        {
            X509Certificate2 cert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "cert",
                    Configuration["Certificates:CerPath"]),
                Configuration["Certificates:Password"]);

            IdentityModelEventSource.ShowPII = true;

            services.AddIdentityServer()
                //.AddDeveloperSigningCredential()
                .AddSigningCredential(cert)
                .AddInMemoryIdentityResources(Config.GetIdentityResourceResources())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
                .AddProfileService<ProfileService>();
        }

        /// <summary>
        /// Configure Authentication with IdentityServer4 remote
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddAuthentication(IServiceCollection services)
        {
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(opt =>
                {
                    opt.RequireHttpsMetadata = false; // for dev env
                    opt.Authority =
            $"http://{Configuration["Identity:Host"]}:{Configuration["Identity:Port"]}";
                    opt.ApiName = AuthorizationServerConsts.ApiResourceNameList.FirstOrDefault(); // match with
                    opt.ApiSecret = AuthorizationServerConsts.TokenSecurityKey.FirstOrDefault();
                    opt.TokenRetriever = CustomTokenRetriever.FromHeaderAndQueryString;
                    opt.Validate(JwtBearerDefaults.AuthenticationScheme);
                });
        }

        /// <summary>
        /// Configure SignalR
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private static void AddSignalR(IServiceCollection services)
        {
            services.AddSignalR();
        }

        /// <summary>
        /// Configure CORS
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddCors(IServiceCollection services)
        {
            services.AddCors(
                //                options => options.AddDefaultPolicy(option => option
                options =>
                {
                    options.AddDefaultPolicy(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials());
                    options.AddPolicy(
                        DefaultCorsPolicyName,
                        builder => builder
                            .WithOrigins(
                                Configuration["App:CorsOrigins"]
                                    .Split(",", StringSplitOptions.RemoveEmptyEntries)
                                    .Select(o => o.RemovePostFix("/"))
                                    .ToArray()
                            )
                            .AllowCredentials()
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                    );
                });
        }

        /// <summary>
        /// Configure Filter
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddMvc(IServiceCollection services)
        {
            services.AddMvcCore(options =>
            {
                options.Filters.Add(new CorsAuthorizationFilterFactory(DefaultCorsPolicyName));
            }).AddApiExplorer().AddAuthorization().AddJsonFormatters();
        }

        /// <summary>
        /// EFCore UnitOfWork
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddUnitOfWork(IServiceCollection services)
        {
            services.AddUnitOfWork<AuthorizationDbContext>();
        }

        private void AddSwagger(IServiceCollection services)
        {
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Info { Title = "Hicap.AuthorizationServer API", Version = "v1" });
                options.DocInclusionPredicate((docName, description) => true);

                // Define the BearerAuth scheme that's in use
                options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme()
                {
                    Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
                    Name = "Authorization",
                    In = "header",
                    Type = "apiKey"
                });
            });
        }

        /// <summary>
        /// EasyCaching use memory cache
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        private void AddDefaultInMemoryCache(IServiceCollection services)
        {
            //MemoryCache
            services.AddDefaultInMemoryCache();
        }

        /// <summary>
        /// Configure Abp
        /// </summary>
        /// <param name="services">IServiceCollection</param>
        /// <returns></returns>
        /// <remarks>
        /// abp ioc container use AspectCore interceptors
        /// 1.EasyCachingInterceptor
        /// 2.MethodExecuteLoggerInterceptor
        /// </remarks>
        private IServiceProvider AddAbp(IServiceCollection services)
        {
            return services.AddAbp<AuthorizationServerWebApiModule>();
        }

        ///// <summary>
        ///// Configure Swagger
        ///// </summary>
        ///// <param name="services">IServiceCollection</param>
        //private static void AddSwaggerGen(IServiceCollection services)
        //{
        //    services.AddSwaggerGen(c =>
        //    {
        //        c.SwaggerDoc("v1", new Info { Title = "Hicap.AuthorizationServer", Version = "v1" });
        //        c.DocInclusionPredicate((docName, description) => true);
        //    });
        //}

        /// <summary>
        /// Bootstrap Configure
        /// </summary>
        /// <param name="app">IApplicationBuilder</param>
        /// <param name="env">IHostingEnvironment</param>
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            try
            {
                app.UseAbp(); //Initializes ABP framework.

                app.UseIdentityServer();

                app.UseAuthentication();

                app.UseSignalR(routes =>
                {
                    routes.MapHub<NotifyHub>("/Notify");
                });

                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.InjectJavascript("/swagger/ui/on-complete.js");
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Hicap.AuthorizationServer API V1");
                });
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
            }
            catch (Exception e)
            {
                Log.Logger.Fatal(e, "Boot Failed");
            }
        }
    }
}